﻿using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using RoboContainer.Infection;

namespace TimeMachine.Engine.Vcs.Svn
{
	public class SvnDiff : IChangesProcessor
	{
		private readonly IDiffCache diffCache;
		private readonly IDiffParser diffParser;
		private readonly IVcsRepo repo;
		private readonly ISvnExecutor svn;

		public SvnDiff(IVcsRepo repo, IDiffCache diffCache, IDiffParser diffParser, ISvnExecutor svn)
		{
			this.repo = repo;
			this.diffCache = diffCache;
			this.diffParser = diffParser;
			this.svn = svn;
		}

		private void ExecSvnDiff(int rev)
		{
			try
			{
				string tmpResultFile = diffCache.GetDiffTemporaryFilename(rev);
				string resultFile = diffCache.GetDiffFilename(rev);
				// чтобы никто даже и не пытался, пока мы тут!
				using(PreventOthersFromReadingOrWritingFile(resultFile))
					svn.ExecuteToFile(tmpResultFile, "diff -c {0} {1}", rev, repo.Url);
				File.Delete(resultFile);
				File.Move(tmpResultFile, resultFile); // быстро-быстро, пока никто не заметил :)
				return;
			}
			catch(Exception e)
			{
				if(IsFirstRevisionError(e.Message))
					File.WriteAllText(diffCache.GetDiffFilename(rev), string.Empty);
				else throw;
			}
		}

		private static FileStream PreventOthersFromReadingOrWritingFile(string resultFile)
		{
			return File.Open(resultFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
		}

		private static bool IsFirstRevisionError(string message)
		{
			// Для самой первой ревизии diff падает с ошибкой о том, что предыдущей ревизии нет в svn-е.
			// Этот случай решил обрабатывать, полагаясь на сообщение об ошибке.
			var messages =
				new[]
					{
						"was not found in the repository at revision",
						"Unable to find repository location for"
					};
			return messages.Any(message.Contains);
		}

		public void ProcessChanges(int rev, ProcessChangesParams processParams)
		{
			if(!diffCache.HasDiff(rev))
				ExecSvnDiff(rev);
			var s = new FileStream(diffCache.GetDiffFilename(rev), FileMode.Open);
			diffParser.Parse(() => new StreamReader(s, Encoding.GetEncoding(1251)), processParams);
		}
	}

	public class FilesystemDiffCache : IDiffCache
	{
		private readonly string cachePath;

		[ContainerConstructor]
		public FilesystemDiffCache(IProjectSettings projectSettings)
			: this(projectSettings.GetDataDirPath())
		{
		}

		public FilesystemDiffCache(string cachePath)
		{
			this.cachePath = cachePath;
		}

		public bool HasDiff(int rev)
		{
			string dataFile = GetDiffFilename(rev);
			return File.Exists(dataFile);
		}

		public string GetDiffFilename(int rev)
		{
			string revString = rev.ToString("0000000", NumberFormatInfo.InvariantInfo);
			return cachePath + Path.DirectorySeparatorChar + revString + ".diff";
		}

		public string GetDiffTemporaryFilename(int rev)
		{
			string revString = rev.ToString("0000000", NumberFormatInfo.InvariantInfo);
			return cachePath + Path.DirectorySeparatorChar + revString + ".diff.tmp";
		}
	}

	public interface IDiffCache
	{
		bool HasDiff(int rev);
		string GetDiffFilename(int rev);
		string GetDiffTemporaryFilename(int rev);
	}
}